﻿package 
{
	import flash.data.SQLConnection;
	import flash.data.SQLMode;
	import flash.display.NativeMenu;
	import flash.display.NativeMenuItem;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.events.SQLEvent;
	import flash.events.TimerEvent;
	import flash.filesystem.File;
	import flash.filesystem.FileMode;
	import flash.filesystem.FileStream;
	import flash.net.FileFilter;
	import flash.utils.Timer;
	import me.nerik.util.Stopwatch;
	
	/**
	 * ...
	 * @author erik
	 */
	public class Main extends Sprite 
	{
		
		private var ui:MainViewUI;
		private var inputFile:File;
		private var outputFile:File;
		private var tmpDbFile:File;
		
		private var sqlConn:SQLConnection;
		
		private var statements:/*Statement*/Array;
		private var num:int;
		private var current:int;
		private var batchNum:int;
		private var batchCurrent:int;
		private var timer:Timer;
		private var defaultBatchSize:Number;
		private var defaultBatchTime:Number;
	
		
		private static const CONVERT_BATCH_SIZE:Number = 1000;
		private static const CONVERT_BATCH_TIME:Number = 500;
		private static const SAVE_BATCH_SIZE:Number = 400;
		private static const SAVE_BATCH_TIME:Number = 1200;		
		
		
		private static const FILE_FILTERS:Array = [new FileFilter("MySQL dump (*.sql)", "*.sql"), new FileFilter("all (*.*)", "*.*")];
		private var inputFileContent:String;
		private var statusMsg:String;
		
		private var excludeTables:Array = new Array();
		private var thenSave:Boolean;
		private var converting:Boolean;
		private var saving:Boolean;
		
		
		public function Main():void 
		{
	
			statements = new Array();
			
			ui = new MainViewUI();
			ui.addEventListener(MainView.INPUT_BROWSE, onUIBrowse);
			ui.addEventListener(MainView.CONVERT, onUIConvert);
			ui.addEventListener(MainView.SAVE, onUISave);
			ui.addEventListener(MainView.SAVE_AS, onUISave);
			

			addChild(ui);
			
			inputFile = new File();
			inputFile.addEventListener(Event.SELECT, onUIBrowseDone);		
			
			tmpDbFile = File.applicationStorageDirectory.resolvePath("tmp.db");

			createNativeMenu();
			
			Params.instance.addEventListener(Params.LOADED, onParamsLoaded);
			Params.instance.initialize();

		}
		
		/**
		 * WIP
		 * @param	e
		 */
		private function onParamsLoaded(e:Event):void 
		{
			
			ui.excludeTablesInput.text = (Params.instance.excludeTablesStr) ? Params.instance.excludeTablesStr : "class, class_in_school, school, session, settings, settings_for_student, student, student_in_class, student_in_session, targetword_by_student, teacher, teacher_in_school, theme_for_class, theme_for_student";
			
			Statement.EXCLUDE_TABLES = ui.excludeTablesInput.text.replace(/\s/gis, "").split(/,|;/);;
			
			if (Params.instance.outputFile)
			{
				outputFile = new File(Params.instance.outputFile);
				ui.outputBrowseTf.text = outputFile.nativePath;
			}
		}
		

		private function createNativeMenu():void
		{
			var menu:NativeMenu = new NativeMenu();
			var nmiMain:NativeMenu = new NativeMenu();
			var nmiInput:NativeMenuItem = new NativeMenuItem("Browse input...");
			nmiInput.addEventListener(Event.SELECT, onUIBrowse);
			nmiInput.keyEquivalent = "o";
			var nmiConvert:NativeMenuItem = new NativeMenuItem("Convert");
			nmiConvert.addEventListener(Event.SELECT, onUIConvert);
			nmiConvert.keyEquivalent = "t";			
			var nmiSave:NativeMenuItem = new NativeMenuItem("Save");
			nmiSave.addEventListener(Event.SELECT, onUISave);
			nmiSave.keyEquivalent = "s";

			
			var nmiAbout:NativeMenuItem = new NativeMenuItem("");
		
			nmiMain.addItem(nmiInput);
			nmiMain.addItem(nmiConvert);
			nmiMain.addItem(nmiSave);			
			menu.addSubmenu(nmiMain, "Conversion");
			menu.addSubmenu(null, "Help");
			stage.nativeWindow.menu = menu;
			
		}

		
		/**
		 * WIP
		 */
		private function saveParams():void 
		{
			
			//TBD
			//Params.instance.excludeTablesStr = 
			
		}
		
		
		//--------------------------- UI events --------------------------------------------------
		private function onUIBrowse(e:Event):void 
		{
			
			var t:String = e.type;
			
			if (e.target is NativeMenuItem)
			{ 
				return;
			}

			inputFile.browseForOpen("Browse for MySQL dump...", FILE_FILTERS);

		}
		
		
		private function onUIConvert(e:Event):void 
		{
			trace("Main::onUIConvert");
			
			saveParams();
			convert();
			
			
		}
		

		
		private function onUISave(evt:Event):void 
		{
			//trace("Main::onUISave");
			
			saveParams();
			
			if (ui.outputBrowseTf.text == "" || evt.type == MainView.SAVE_AS)
			{
				//prompt for target
				var someFile:File = new File();
				someFile.addEventListener(Event.SELECT, onTmpOutputFileBrowseDone);
				someFile.browseForSave("Save SQLite db as...");
			}
			else
			{
				//save to already set target
				convertAndSave();
			}
			
		}
				
		
		
		//--------------------------- File events --------------------------------------------------		
		private function onUIBrowseDone(e:Event):void 
		{
			//trace("Main::onUIBrowseDone");
			
			if (e.target === inputFile) 	
			{
				ui.inputFile = inputFile.nativePath;
				inputFileContent = readInputFileContent();
				//ui.inputTa.text = inputFileContent; //TODO disabled because the input component crashes when displaying a huge chunk of text
				
			}
			else 
			{
				ui.outputFile = outputFile.nativePath;
			}

		}
		
		private function onTmpOutputFileBrowseDone(e:Event):void 
		{
			//trace("Main::onTmpOutputFileBrowseDone");
			 
			var f:File = e.target as File;
			
			outputFile = f;
			
			//update ui
			ui.outputBrowseTf.text = f.nativePath;
			
			Params.instance.save();
			
			//write db
			convertAndSave();
		}		
		
		
		private function onWriteError(f:File):void
		{
			ui.setStatus("File \"" + f.name + "\" is read-only ", true);
			ui.enable();
		}	
		
		
		
		
		
		//--------------------------- SQL Events --------------------------------------------------		
		
		
		
		
		private function readInputFileContent():String
		{
			var fs:FileStream = new FileStream();
			fs.open(inputFile, FileMode.READ);
			var r:String = fs.readUTFBytes(fs.bytesAvailable);
			fs.close();
			return r;
		}		
		

		
		
		//----------------------------Core------------------------------------------------
		
		private function convert():void
		{
			ui.disable();
			
			doConvert();
		}
		
		
		private function convertAndSave():void
		{
		
			ui.disable();
			thenSave = true;
			
			doConvert();
		}
				
		
		
		
		private function doConvert():void
		{

			var srcContent:String;	
			
			if (ui.inputDirectRadio.selected)
			{
				srcContent = ui.inputTa.text;
				if (srcContent.replace(/\s/gis, "") == "") 
				{
					ui.setStatus("Direct input field is empty", true);
					return;
				}
			}
			else
			{ 
				if (ui.inputBrowseTf.text == "" || !inputFile.exists)
				{
					ui.setStatus("Input file doesn't exist", true);
					return;
				}
				
				srcContent = readInputFileContent();
			}
			
			
			resetStatements();
			
			defaultBatchSize = CONVERT_BATCH_SIZE;
			defaultBatchTime = CONVERT_BATCH_TIME;
			converting = true;			
			
			ui.setStatus("Converting started...");
			
			current = 0;
			
			timer = new Timer(defaultBatchTime, 1);
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, convertBatch);
			
			Stopwatch.start("");
			

			var statementsSrc:Array = srcContent.split(";");
			num = statementsSrc.length;
		
			
			for (var i:int = 0; i < num; i++) 
			{

				statements.push(new Statement(statementsSrc[i]));
			}

			Stopwatch.stop()

			convertBatch();
		
			

		}
		
		private function resetStatements():void 
		{
			//TODO clean stuff properly
			statements = new Array();
		}
		
		
				
		
		
		private function convertBatch(e:TimerEvent = null):void
		{
			
			batchNum = Math.min(num - current, defaultBatchSize);
			
			var currentStatement:Statement;
			
			for (var i:int = 0; i < batchNum; i++) 
			{
				currentStatement = statements[current + i];
				currentStatement.convert();	
				
				
			}
			current += batchNum;
			
			statusMsg = "Converting: " + Math.floor((current / num) * 100) + "% done [" + current + "/" + num + "]";
			ui.setStatus(statusMsg);
			
			if (batchNum < defaultBatchSize)
			{
				finishConvert();
			}
			else
			{
				//triggers a new convertBatch()
				timer.reset();
				timer.start();
			}
		
			
			
		}
		
		private function finishConvert():void 
		{
			converting = false;
			
			
			ui.enable();
			
		
			
			//var _converted:String = "";
			var st:Statement;
			
		
			for (var i:int = num -1; i >= 0; i--) 
			{
				st = statements[i];
				
				if (st.empty)  
				{
					//rmIndexes.push(i);
					statements.splice(i, 1);
				}
				else 
				{
					//TODO...
					//statements[i].text += ";";
					//_converted +=  statements[i].text + "\n";

				}
			}
			
			
			
			
			//ui.inputTa.text = _converted; //TODO displaying too big text crashes
			
			ui.setStatus("SQL converted, "+ num + " statements");
			
			if (thenSave)
			{
				thenSave = false;
				doSave();
			}
		}
		
		
		
		

		
		
		
		
		//-----------------------------------Saving-----------------------------------------------
		private function doSave():void		
		{
			try 
			{
				if (tmpDbFile.exists) tmpDbFile.deleteFile();
			}
			catch (err:Error)
			{
				onWriteError(tmpDbFile);
				return;
			}
			

			
			sqlConn = new SQLConnection();
			sqlConn.openAsync(tmpDbFile, SQLMode.CREATE);
			sqlConn.addEventListener(SQLEvent.OPEN, startSaving);
		}
		
		
		private function startSaving(e:SQLEvent = null):void
		{
			defaultBatchSize = SAVE_BATCH_SIZE;
			defaultBatchTime = SAVE_BATCH_TIME;
			saving = true;
			
			ui.setStatus("Saving started...");
			
			current = 0;
			
			timer = new Timer(defaultBatchTime, 1);
			timer.addEventListener(TimerEvent.TIMER_COMPLETE, saveBatch);
			
			saveBatch();
			
		}
		
		
		private function saveBatch(e:TimerEvent = null):void
		{
			batchNum = Math.min(num - current, defaultBatchSize);
			batchCurrent = 0;
			
			var currentStatement:Statement;
			
			for (var i:int = 0; i < batchNum; i++) 
			{
				currentStatement = statements[current + i];
				//currentStatement.sqlDone.addOnce(onSQLDone);
				currentStatement.sql.addEventListener(SQLEvent.RESULT, onSQLDone);
				currentStatement.sqlDone.addOnce(onSQLError);
				currentStatement.save(sqlConn);				
			}
		
		}
		
		private function onSQLError(error:String):void 
		{
		
			ui.setStatus(error, true);
			trace(error);
			ui.enable();
			timer.stop();
		}
		
		

		
		
		private function onSQLDone(e:Event = null):void 
		{
		
			
			statusMsg = "Saving: " + Math.floor((current / num) * 100) + "% done [" + current + "/" + num + "]";
			
			//if (sqlIsLong) statusMsg += " You might wanna grab a cuppa coffee mate";
			
			ui.setStatus(statusMsg);
			//trace("Main::onSQLResult " + sqlQueueCurrent +"/" + sqlQueueLength + ",  " + numSqlStatementsDone + "/" + numSqlStatements /*+ " - " + (e.target as SQLStatement).text*/);
			
			if (batchCurrent == batchNum - 1)
			{
				
				if (current == num - 1)
				{
					finishSave();
				}
				else
				{
					timer.reset();
					timer.start();
					
				}
			}
		
			batchCurrent++;
			current++;
		}
		
		
		
		private function finishSave():void
		{
			//trace("Main::finishSave");
			sqlConn.close();
			saving = false;
			
			try 
			{ 
				tmpDbFile.copyTo(outputFile, true);
			}
			catch (err:Error)
			{
				onWriteError(outputFile);
				return;
			}
			
			
			ui.setStatus("SQL and saved as " + outputFile.name +  ", " + num + " statements");
			ui.enable();
			
			if (ui.openWhenConvert)
			{
				outputFile.openWithDefaultApplication();
			}
			
			
		}
	
		
		
		

		
	}
	
}